"""
@File    :   service.py
@Time    :   2024/03/13 16:14:09
@Author  :   Qrj
@Email   :   923207736@qq.com
@description   :   租户的curd相关方法类
"""

from datetime import datetime

from cashews import NOT_NONE
from sqlalchemy import and_, delete, func, insert, or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession

from src.business.admin.system.oauth2.auth.enums import OAuth2GrantTypeEnum
from src.business.admin.system.oauth2.client.models import OAuth2Client
from src.business.admin.system.settings.models import SystemSettings
from src.business.admin.system.settings.schemas.validate import (
    SystemSettingsCreateValidateSchema,
)
from src.business.admin.system.tenant.package.models import TenantPackage
from src.business.admin.system.user.models import User
from src.config import get_app_settings
from src.exceptions.custom_async_validator import generate_async_validator_exception
from src.schemas.validate import (
    OrderBySearchValidateSchema,
    PaginationSearchValidateSchema,
)
from src.utils.cashews import CashewsCondition, data_cache
from src.utils.password_encryption import PswHashUtil

from .models import Tenant
from .schemas.out import (
    ListTenantCompleteOutSchema,
    ListTenantLoginPageSelectOutSchema,
    TenantCompleteOutSchema,
    TenantLoginPageSelectOutSchema,
)
from .schemas.validate import (
    TenantCreateValidateSchema,
    TenantDeleteValidateSchema,
    TenantListSearchValidateSchema,
    TenantUpdateStatusValidateSchema,
    TenantUpdateValidateSchema,
)

settings = get_app_settings()


class TenantService:
    """租户业务逻辑类"""

    @data_cache(
        ttl="30m",
        key="tenant#id:{tenant_id}",
        tags=["tenant#id", "tenant#id:{tenant_id}"],
        condition=NOT_NONE,
    )
    @staticmethod
    async def get_tenant_use_id(
        db_session: AsyncSession,
        tenant_id: int,
        raise_not_exists_exception: bool = True,
        raise_disabled_exception: bool = True,
        raise_expired_exception: bool = True,
    ) -> TenantCompleteOutSchema | None:
        """获取租户信息实例(主键ID)

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int): 租户ID
            raise_not_exists_exception (bool, optional): 没找到的话是否直接报错
            raise_disable_exception (bool, optional): 禁用的话是否直接报错
            raise_expire_exception (bool, optional): 过期的话是否直接报错

        Returns:
            TenantCompleteOutSchema | None: 租户信息实例
        """
        # 租户信息查询sql
        search_sql = (
            select(
                *TenantCompleteOutSchema.convert_to_sql_model_field(
                    Tenant, exclude_field=("tenant_package_name",)
                ),
                func.ifnull(TenantPackage.name, "").label("tenant_package_name"),
            )
            .outerjoin(TenantPackage, TenantPackage.id == Tenant.tenant_package_id)
            .where(Tenant.id == tenant_id)
        )
        async with db_session.begin():
            search_res = await db_session.execute(search_sql)
        result = search_res.mappings().first()
        if result is not None:
            result = TenantCompleteOutSchema.model_validate(result)
            if (not result.status) and raise_disabled_exception:
                # 禁用则报错
                raise generate_async_validator_exception("id", "租户已被禁用")
            if (
                (not result.is_system)
                and result.expiration_datetime < datetime.now()
                and raise_expired_exception
            ):
                # 非系统级别用户, 过期则报错
                raise generate_async_validator_exception("id", "租户已过期")
            return result
        elif result is None and raise_not_exists_exception:
            # 未找到则报错
            raise generate_async_validator_exception("id", "租户不存在")
        else:
            return result

    @data_cache(
        ttl="30m",
        key="complete_tenant_list:{search_schema}:{pagination_schema}:{order_schema}",
        tags=["complete_tenant_list"],
        condition=CashewsCondition.condition_only_true_result,
    )
    @staticmethod
    async def list_search_complete_tenant(
        db_session: AsyncSession,
        search_schema: TenantListSearchValidateSchema,
        pagination_schema: PaginationSearchValidateSchema,
        order_schema: OrderBySearchValidateSchema,
        need_select_total: bool = True,
    ) -> tuple[list[TenantCompleteOutSchema], int | None]:
        """完整信息的租户列表查询

        Args:
            db_session (AsyncSession): 数据库连接池
            search_schema (TenantPackageListSearchValidateSchema): 查询参数
            pagination_schema: 分页参数
            order_schema: 排序参数
            need_select_total: 是否需要查询总数

        Returns:
            tuple[list[TenantCompleteOutSchema], int | None]: 查询结果
        """
        # 构建排序字段
        order_by_field_sql = order_schema.convert_to_order_by_sql(search_schema, Tenant)
        # 构建查询sql
        list_search_sql = (
            select(
                *TenantCompleteOutSchema.convert_to_sql_model_field(
                    Tenant, exclude_field=("tenant_package_name",)
                ),
                func.ifnull(TenantPackage.name, "").label("tenant_package_name"),
            )
            .outerjoin(TenantPackage, TenantPackage.id == Tenant.tenant_package_id)
            .where(and_(*search_schema.convert_to_where_sql(Tenant)))
            .order_by(order_by_field_sql)
            .offset(pagination_schema.offset)
            .limit(pagination_schema.limit)
        )
        # 开始查询
        async with db_session.begin():
            list_search_res = await db_session.execute(list_search_sql)
        result = list_search_res.mappings().all()
        # 根据need_select_total参数来确定是否需要查询总数
        if need_select_total:
            total_sql = (
                select(func.count())
                .select_from(Tenant)
                .where(and_(*search_schema.convert_to_where_sql(Tenant)))
            )
            async with db_session.begin():
                total_res = await db_session.execute(total_sql)
            total = total_res.scalar()
        else:
            total = None
        # 统一转为pydantic列表模型
        return ListTenantCompleteOutSchema.validate_python(result), total

    @data_cache(
        ttl="30m",
        key="login_page_select_tenant_list",
        tags=["login_page_select_tenant_list"],
        condition=CashewsCondition.condition_only_true_result,
    )
    @staticmethod
    async def list_get_login_page_select_tenant(
        db_session: AsyncSession,
    ) -> list[TenantLoginPageSelectOutSchema]:
        # 构建sql
        list_sql = (
            select(*TenantLoginPageSelectOutSchema.convert_to_sql_model_field(Tenant))
            .where(
                Tenant.status.is_(True),
                or_(
                    Tenant.expiration_datetime > datetime.now(),
                    Tenant.is_system.is_(True),
                ),
            )
            .order_by(Tenant.show_order)
        )
        # 进行sql查询
        async with db_session.begin():
            list_res = await db_session.execute(list_sql)
        # 统一转换结果为pydantic模型
        result = ListTenantLoginPageSelectOutSchema.validate_python(
            list_res.mappings().all()
        )
        return result

    @staticmethod
    @data_cache.transaction()
    async def create_tenant(
        db_session: AsyncSession,
        create_schema: TenantCreateValidateSchema,
        creator_username: str = "",
        is_system: bool = False,
    ) -> int:
        """创建租户

        Args:
            db_session (AsyncSession): 数据库连接池
            create_info (TenantCreateValidateSchema): 创建信息
            creator_username: 创建者用户名
            is_system: 是否系统级别租户

        Returns:
            int: 创建的sql主键ID
        """
        # 构建租户的创建信息字典
        create_info_dict = create_schema.model_dump(
            exclude=("admin_username", "admin_password", "admin_name")
        )
        # 系统级别租户, 将其租户套餐ID改为0
        if is_system:
            create_info_dict["tenant_package_id"] = 0
        else:
            # 检查租户套餐是否存在
            await TenantService.verify_tenant_package_exists(
                db_session, create_schema.tenant_package_id
            )
        # 创建sql
        create_sql = insert(Tenant).values(
            creator_username=creator_username,
            is_system=is_system,
            **create_info_dict,
        )
        async with db_session.begin():
            # 创建
            create_res = await db_session.execute(create_sql)
            created_tenant_id = create_res.inserted_primary_key_rows[0][0]
            # 创建租户的系统配置
            create_settings_dict = SystemSettingsCreateValidateSchema().model_dump()
            create_settings_sql = insert(SystemSettings).values(
                tenant_id=created_tenant_id, **create_settings_dict
            )
            await db_session.execute(create_settings_sql)
            # 创建租户的管理员用户
            # 获取密码hash
            password_hash = await PswHashUtil.async_hash_psw(
                create_schema.admin_password
            )
            # 创建租户的默认管理员用户
            create_user_sql = insert(User).values(
                tenant_id=created_tenant_id,
                password_hash=password_hash,
                creator_username=creator_username,
                is_admin=True,
                username=create_schema.admin_username,
                name=create_schema.admin_name,
                remark=create_schema.admin_remark,
            )
            created_admin_user_res = await db_session.execute(create_user_sql)
            created_admin_user_id = created_admin_user_res.inserted_primary_key_rows[0][
                0
            ]
            # 修改租户的管理员信息
            update_tenant_admin_user_info_sql = (
                update(Tenant)
                .values(
                    admin_user_id=created_admin_user_id,
                    admin_username=create_schema.admin_username,
                    admin_remark=create_schema.admin_remark,
                )
                .where(Tenant.id == created_tenant_id)
            )
            await db_session.execute(update_tenant_admin_user_info_sql)
            # 创建租户的初始Oauth2客户端(password、swagger_password)
            create_oauth2_client_sql = insert(OAuth2Client).values(
                tenant_id=created_tenant_id,
                creator_username=creator_username,
                is_init=True,
                client_id=settings.default_oauth2_password_module_client_id,
                client_secret=settings.default_oauth2_password_module_client_secret,
                name="初始Oauth2客户端",
                remark="当前租户的初始Oauth2客户端, 不受过期时间字段约束, 仅支持密码模式与swagger密码模式",
                expiration_datetime=datetime.now(),
                authorized_grant_types=[
                    OAuth2GrantTypeEnum.password.value,
                    OAuth2GrantTypeEnum.swagger_password.value,
                ],
            )
            await db_session.execute(create_oauth2_client_sql)
            # 清空相关缓存标签
            await data_cache.delete_tags(
                "login_page_select_tenant_list", "complete_tenant_list"
            )
        return created_tenant_id

    @staticmethod
    @data_cache.transaction()
    async def update_tenant(
        db_session: AsyncSession,
        update_schema: TenantUpdateValidateSchema,
        updater_username: str = "",
    ):
        """修改租户信息

        Args:
            db_session (AsyncSession): 数据库连接池
            update_schema (TenantUpdateValidateSchema): 修改操作所需的数据
            updater_username (str, optional):修改者用户名
        """
        # 检查租户套餐是否存在
        await TenantService.verify_tenant_package_exists(
            db_session, update_schema.tenant_package_id
        )
        # 检查需修改的sql实例是否存在(禁用不报错、过期不报错)
        update_sql_obj = await TenantService.get_tenant_use_id(
            db_session,
            update_schema.id,
            raise_disabled_exception=False,
            raise_expired_exception=False,
        )
        # 检查是否修改了系统级别的租户
        if update_sql_obj.is_system:
            raise generate_async_validator_exception("id", "不能修改系统级别的租户")
        # 构建修改所需的数据(排除未设置的值和id)
        update_schema_data = update_schema.model_dump(
            exclude=("id"), exclude_unset=True
        )
        if update_schema_data:
            # 修改sql
            update_sql = (
                update(Tenant)
                .values(**update_schema_data, updater_username=updater_username)
                .where(Tenant.id == update_schema.id)
            )
            # 修改操作
            async with db_session.begin():
                # 修改sql数据
                await db_session.execute(update_sql)
                # 清空相关缓存标签
                await data_cache.delete_tags(
                    f"tenant#id:{update_schema.id}",
                    "login_page_select_tenant_list",
                    "complete_tenant_list",
                )

    @staticmethod
    @data_cache.transaction()
    async def update_tenant_status(
        db_session: AsyncSession,
        update_schema: TenantUpdateStatusValidateSchema,
        updater_username: str = "",
    ):
        """单独修改租户状态

        Args:
            db_session (AsyncSession): 数据库连接池
            update_schema (TenantUpdateStatusValidateSchema): 修改操作所需的数据
            updater_username (str, optional):修改者用户名
        """
        # 检查需修改的sql实例是否存在(禁用不报错、过期不报错)
        update_sql_obj = await TenantService.get_tenant_use_id(
            db_session,
            update_schema.id,
            raise_disabled_exception=False,
            raise_expired_exception=False,
        )
        # 检查是否修改了系统级别的租户
        if update_sql_obj.is_system:
            raise generate_async_validator_exception("id", "不能修改系统级别的租户")
        # 修改sql
        update_sql = (
            update(Tenant)
            .values(updater_username=updater_username, status=update_schema.status)
            .where(Tenant.id == update_schema.id)
        )
        # 修改操作
        async with db_session.begin():
            # 修改sql数据
            await db_session.execute(update_sql)
            # 清空相关缓存标签
            await data_cache.delete_tags(
                f"tenant#id:{update_schema.id}",
                "login_page_select_tenant_list",
                "complete_tenant_list",
            )

    @staticmethod
    @data_cache.transaction()
    async def delete_tenant(
        db_session: AsyncSession,
        delete_schema: TenantDeleteValidateSchema,
    ):
        """删除租户(其他业务数据由funboost异步任务队列删除)
        不建议删除(不用的话禁用就好了)

        Args:
            db_session (AsyncSession): 数据库连接池
            delete_schema (TenantDeleteValidateSchema): 删除操作所属的数据
        """
        # 查找需删除的租户
        # 构建查询sql
        select_sql = select(Tenant.id, Tenant.is_system).where(
            Tenant.id.in_(delete_schema.ids)
        )
        # 进行sql查询
        async with db_session.begin():
            select_res = await db_session.execute(select_sql)
        select_result = select_res.mappings().all()
        # 检查是否删除了系统级别的租户
        if any([i["is_system"] for i in select_result]):
            raise generate_async_validator_exception("ids", "不允许删除系统级别的租户")
        # 获取需要删除的租户id列表
        delete_id_list = [i["id"] for i in select_result]
        # 进行删除操作
        if select_result:
            # 删除租户本身的sql
            delete_sql = delete(TenantPackage).where(
                TenantPackage.id.in_(delete_id_list)
            )
            # 删除操作
            async with db_session.begin():
                # 删除sql中的数据
                await db_session.execute(delete_sql)
                # 清空相关缓存标签
                await data_cache.delete_tags(
                    *[f"tenant#id:{i}" for i in delete_id_list],
                    "login_page_select_tenant_list",
                    "complete_tenant_list",
                )

    @staticmethod
    async def verify_tenant_package_exists(
        db_session: AsyncSession, tenant_package_id: int
    ):
        """验证租户套餐是否存在

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_package_id (int): 租户套餐ID
        """
        # 检查租户套餐是否存在
        from src.business.admin.system.tenant.package.service import (
            TenantPackageService,
        )

        await TenantPackageService.get_tenant_package_use_id(
            db_session, tenant_package_id, raise_disabled_exception=False
        )
